/*
 * @Author: xiaosihan
 * @Date: 2021-04-11 21:54:31
 * @Last Modified by: xiaosihan
 * @Last Modified time: 2021-11-07 19:28:50
 */

import _ from "lodash";
import React from "react";
import * as THREE from "three";
import { BufferGeometry, Material, NormalMapTypes, RawShaderMaterial, ShaderMaterial, Texture } from "three";
import meshCache from "../cache/meshCache";
import textureCache from "../cache/textureCache";
import Object3D from "../Object3D/Object3D";
import Object3DComponent, { Object3DComponentProps } from "../Object3D/Object3DComponent";
import { Vector2 } from "../Object3D/type";
import { Object3DEvent } from "../Renderer/type";
import Transition from "../xrt_util/Transition";
import TransitionColor from "../xrt_util/TransitionColor";
import MeshSeat from "./MeshSeat";

export interface MeshProps extends Object3DComponentProps {
  /**
   * 网格模型 例如 THREE.BufferGeometry | "../a.obj"
   */
  geometry?: BufferGeometry | string;
  /**
   * 
   */
  material?: | "MeshBasicMaterial"
  | "MeshDepthMaterial"
  | "MeshDistanceMaterial"
  | "MeshLambertMaterial"
  | "MeshMatcapMaterial"
  | "MeshNormalMaterial"
  | "MeshPhongMaterial"
  | "MeshPhysicalMaterial"
  | "MeshStandardMaterial"
  | "MeshToonMaterial"
  | ShaderMaterial
  | RawShaderMaterial
  | Material;
  /**
   * 纹理 例如 THREE.Texture | "../a.jpg"
   */
  map?: THREE.Texture | string;
  /**
   * 颜色叠加 例: "#f00"
   */
  color?: string;
  /**
   * 需要显示的正反面例: THREE.BackSide  THREE.FrontSide  THREE.DoubleSide
   */
  side?: THREE.Side;
  /**
   * 透明度 取值范围 0-1
   */
  opacity?: number;
  /**
   * 以网格的方式绘制模型 true | false
   */
  wireframe?: boolean; //----------------------------材质以网格的方式绘制
  /**
   *深度测试 为false 不被其他模型挡住
   */
  depthTest?: boolean;
  /**
   *深度写入 为false时 不挡住其他模型
   */
  depthWrite?: boolean;
  /**
   *透明过滤阀值
   */
  alphaTest?: number;

  /**
   *透明通道纹理
   */
  alphaMap?: string | Texture;
  alphaToCoverage?: boolean; // 透明通道的范围

  emissive?: string; //------------------------------发光颜色
  emissiveIntensity?: number; //---------------------发光强度
  emissiveMap?: string | Texture; //-----------------发光纹理

  specular?: string; //------------------------------反光颜色
  specularMap?: string | Texture; //------------------反光纹理

  roughness?: number; //-----------------------------粗糙度
  roughnessMap?: string | Texture; //----------------粗糙度纹理

  metalness?: number; //-----------------------------光泽度
  metalnessMap?: string | Texture; //----------------光泽度纹理

  aoMap?: string | Texture; //-----------------------细节纹理
  aoMapIntensity?: number; //------------------------细节纹理强度

  lightMap?: string | Texture; //--------------------光照纹理
  lightMapIntensity?: number; //---------------------光照纹理强度

  bumpScale?: number; //-----------------------------凹凸纹理缩放
  bumpMap?: string | Texture; //----------------------凹凸纹理

  transmission?: number; //--------------------------传输
  transmissionMap?: string | Texture; //--------------传输纹理

  envMap?: string | Texture; //----------------------环境贴图
  envMapIntensity?: number; //-----------------------环境贴图强度
  envMapRefraction?: boolean; //----------------------反射环境贴图

  normalMap?: string | Texture; //-------------------法线纹理
  normalMapType?: NormalMapTypes; //-----------------法线的类型
  normalScale?: Vector2;

  displacementMap?: string | Texture; //-------------距离纹理
  displacementScale?: number; //---------------------距离纹理缩放
  displacementBias?: number; //----------------------距离纹理的的偏移量用于处理模糊纹理

  refractionRatio?: number; //-----------------------折射率
  flatShading?: boolean; //--------------------------平面阴影
}

// 模型加载状态
export enum LoadState {
  LOADDING = "loadding", // 加载中
  LOADED = "loaded", // 加载完成
  LOAD_ERR = "load_err", // 加载失败
  EMPTY = "empty", // 未设置模型
}

interface State {
  loadState: LoadState;
}

const EmptyGeometry = new THREE.BufferGeometry();

export default class Mesh<P = {}, S = {}> extends Object3DComponent<P & MeshProps, State> {
  constructor(props: P & MeshProps) {
    super(props);

    this.object3d = new THREE.Mesh() as unknown as Object3D & THREE.Mesh;
  }

  state: State = {
    loadState: LoadState.LOADED,
  };

  declare object3d: Object3D;

  // 颜色缓动对象
  Tcolor = new TransitionColor((this.props.color as string) || "#fff");
  //发光颜色
  Temissive = new TransitionColor((this.props.emissive as string) || "#000");
  // 反射颜色
  Tspecular = new TransitionColor((this.props.specular as string) || "#000");

  // 其他数值型参数
  otherParams = new Transition(); // 其他参数

  // 环境纹理
  envMap?: Texture;



  componentDidMount() {
    // 设置模型
    this.setGeometry();
    //设置材质
    this.setMaterial();

    this.object3d.addEventListener(Object3DEvent.BEFORE_RENDER, this.setTransfromParame);
  }

  componentDidUpdate() {
    // 设置模型
    this.setGeometry();
    //设置材质
    this.setMaterial();

    this.object3d.removeEventListener(Object3DEvent.BEFORE_RENDER, this.setTransfromParame);
    this.object3d.addEventListener(Object3DEvent.BEFORE_RENDER, this.setTransfromParame);
  }

  componentWillUnmount() {
    const { material } = this.props;
    // 回收材质
    if (_.isString(material)) {
      this.object3d.material.dispose();
    }

    this.object3d.removeEventListener(Object3DEvent.BEFORE_RENDER, this.setTransfromParame);
  }

  //设置模型
  setGeometry() {
    const { geometry } = this.props;
    const { objLoader } = this.context;

    // 没有设置模型
    if (_.isUndefined(geometry)) {
      this.setState({ loadState: LoadState.EMPTY });
      this.object3d.geometry = EmptyGeometry;
      return;
    }

    // 传入模型对象直接赋值
    if (_.get(geometry, ["isBufferGeometry"])) {
      this.object3d.geometry = geometry as BufferGeometry;
      this.setState({ loadState: LoadState.LOADED });
      return;
    }

    // 如果传入的是模型的加载路径
    if (_.isString(geometry)) {
      // 缓存中没有 就加载来存入缓存中
      let Geo = meshCache.get(geometry);

      if (Geo) {
        this.object3d.geometry.dispose();
        this.object3d.geometry = Geo;
        this.setState({ loadState: LoadState.LOADED });
      } else {
        this.setState({ loadState: LoadState.LOADDING });

        Geo = new BufferGeometry();

        meshCache.set(geometry, Geo);

        this.object3d.geometry.dispose();
        this.object3d.geometry = Geo;

        objLoader.load(
          geometry,
          (mesh) => {
            let geo = _.get(mesh, ["children", 0, "geometry"]);
            Object.assign(Geo, geo);

            this.setState({ loadState: LoadState.LOADED });
          },
          (progress: any) => { },
          (err: any) => this.setState({ loadState: LoadState.LOAD_ERR })
        );
      }
    }
  }

  //设置材质
  setMaterial() {
    const {
      bezier = [0.8, 0, 0.2, 1],
      duration = 500,
      material = "MeshBasicMaterial", // --材质类型
      color = "#fff", // --------------------------------叠加颜色
      opacity = 1, // -----------------------------------透明度
      side = THREE.DoubleSide, // -----------------------需要展示的面
      map,
      wireframe = false, // -----------------------------材质以网格的方式绘制
      depthTest = true, // ------------------------------深度测试 为false 不被其他模型挡住
      depthWrite = true, // -----------------------------深度写入  为false时 不挡住其他模型

      alphaTest = 0.1, // ----------------------------------透明过滤值
      alphaMap, //---------------------------------------透明通道纹理
      alphaToCoverage = true,

      emissive = "#000", //------------------------------发光颜色
      emissiveIntensity = 1, //---------------------------发光强度
      emissiveMap, // -----------------------------------发光纹理

      specular = "#000", // -----------------------------反光颜色
      specularMap, //------------------------------------反光纹理

      roughness = 0.5, // -------------------------------粗糙度
      roughnessMap, // ----------------------------------粗糙度纹理

      metalness = 0.5, // -------------------------------光泽度
      metalnessMap, // ----------------------------------光泽度纹理

      aoMap, // -----------------------------------------细节纹理
      aoMapIntensity = 1, //-----------------------------细节强度

      lightMap, // --------------------------------------光照纹理
      lightMapIntensity = 1, // -------------------------光照纹理强度

      bumpScale = 1, // ---------------------------------凹凸纹理缩放
      bumpMap, //----------------------------------------凹凸纹理

      transmission = 1, // ------------------------------传输
      transmissionMap, // -------------------------------传输纹理

      envMap, // ----------------------------------------环境贴图
      envMapIntensity = 1, // ---------------------------环境贴图强度

      normalMap, // -------------------------------------法线纹理
      normalMapType, // ---------------------------------法线的类型
      normalScale = { x: 1, y: 1 }, // ------------------法线的缩放

      displacementMap, // -------------------------------距离纹理
      displacementScale = 1, // -------------------------距离纹理缩放
      displacementBias = 0.01, // -----------------------距离纹理的的偏移量用于处理模糊纹理

      refractionRatio = 1, // ---------------------------折射率
      flatShading = false, // ---------------------------不平滑法线
    } = this.props;

    // 如果是自定义材质就直接赋值
    if (_.get(material, ["isMaterial"])) {
      this.object3d.material = material as Material;
      return;

      // 材质不同时 切换材质
    } else if (this.object3d.material.type !== material) {
      this.object3d.material = new THREE[material as "MeshBasicMaterial"]({});
    }

    // 叠加颜色
    this.Tcolor.setBezier(bezier).setDuration(duration).set(color);

    // 发光颜色
    this.Temissive.setBezier(bezier).setDuration(duration).set(emissive);

    // 反光颜色
    this.Tspecular.setBezier(bezier).setDuration(duration).set(specular);

    // 其他参数
    this.otherParams.setBezier(bezier).setDuration(duration);

    this.otherParams.set({
      opacity, // -------------------------------------透明度
      alphaTest, // ------------------------------------透明过滤值
      emissiveIntensity, //-----------------------------发光强度
      roughness, // -----------------------------------粗糙度
      metalness, // -----------------------------------光泽度
      aoMapIntensity, //-------------------------------未知参数
      lightMapIntensity, // ---------------------------光照纹理强度
      bumpScale, // -----------------------------------凹凸纹理缩放

      //TODO 物理材质加上这个参数会报错后期调研
      //   transmission, // --------------------------------传输
      envMapIntensity, // -----------------------------环境贴图强度
      normalScaleX: normalScale.x || 1, // ------------法线的缩放x
      normalScaleY: normalScale.y || 1, // ------------法线的缩放y
      displacementScale, // ---------------------------距离纹理缩放
      displacementBias, // ----------------------------距离纹理的的偏移量用于处理模糊纹理
      refractionRatio, // -----------------------------折射率
    });

    // 设置 非number 和 texture 类型的参数
    this.setMaterialParam({
      alphaToCoverage,
      side, // -----------------------------------------需要展示的面
      transparent: opacity < 1, //-----------------------------自动开启透明度
      wireframe, // -----------------------------------材质以网格的方式绘制
      depthTest, // -----------------------------------深度测试 为false 不被其他模型挡住
      depthWrite, // ----------------------------------深度写入 // 为false时 不挡住其他模型
      flatShading, // ---------------------------------平面阴影
      normalMapType, // --------------------------------法线的类型
    });

    // 设置纹理
    this.setMap("map", map); //------------------------- 纹理
    this.setMap("alphaMap", alphaMap); // ---------------透明通道纹理
    this.setMap("emissiveMap", emissiveMap); // ---------发光纹理
    this.setMap("specularMap", specularMap); //-----------反光纹理
    this.setMap("roughnessMap", roughnessMap); // -------粗糙度纹理
    this.setMap("metalnessMap", metalnessMap); // -------光泽度纹理
    this.setMap("aoMap", aoMap); // ---------------------未知纹理
    this.setMap("lightMap", lightMap); // ---------------光照纹理
    this.setMap("bumpMap", bumpMap); //-------------------凹凸纹理
    this.setMap("transmissionMap", transmissionMap); //-- 传输纹理
    this.setMap("envMap", envMap); //------------------- 环境贴图
    this.setMap("normalMap", normalMap); //------------- 法线纹理
    this.setMap("displacementMap", displacementMap); //- 距离纹理

    // 设置裁剪面
    super.setClipPlanes();

    this.object3d.material.needsUpdate = true;
  }

  // 设置材质支持的参数
  setMaterialParam(param: any) {
    for (let i in param) {
      if ("normalScaleX" === i) {
        if (
          (this.object3d.material as THREE.MeshStandardMaterial).normalScale
        ) {
          (
            this.object3d.material as THREE.MeshStandardMaterial
          ).normalScale.setX(param["normalScaleX"]);
        }
      } else if ("normalScaleY" === i) {
        if (
          (this.object3d.material as THREE.MeshStandardMaterial).normalScale
        ) {
          (
            this.object3d.material as THREE.MeshStandardMaterial
          ).normalScale.setX(param["normalScaleY"]);
        }
      } else if (this.object3d.material.hasOwnProperty(i)) {
        (this.object3d.material as any)[i] = param[i];
      }
    }

    this.object3d.material.needsUpdate = true;
  }

  // 持续设置缓动参数
  setTransfromParame = () => {
    const { material: propsMaterial } = this.props;

    // 自定义材质就不设置其他参数了
    if (_.get(propsMaterial, ["isMaterial"])) {
      return false;
    }

    let keepRun = false; // 下一帧是否需要继续渲染

    // 叠加颜色
    if (
      (this.object3d.material as THREE.MeshPhongMaterial).color &&
      !this.Tcolor.isDone()
    ) {
      const { r, g, b } = this.Tcolor.getJson();
      (this.object3d.material as THREE.MeshPhongMaterial).color.setRGB(r, g, b);
      keepRun = true;
    }

    // 发光颜色
    if (
      (this.object3d.material as THREE.MeshPhongMaterial).emissive &&
      !this.Temissive.isDone()
    ) {
      const { r, g, b } = this.Temissive.getJson();
      (this.object3d.material as THREE.MeshPhongMaterial).emissive.setRGB(r, g, b);
      keepRun = true;
    }

    // 反光颜色
    if ((this.object3d.material as any).specular && !this.Tspecular.isDone()) {
      const { r, g, b } = this.Tspecular.getJson();
      (this.object3d.material as THREE.MeshPhongMaterial).specular.setRGB(r, g, b);
      keepRun = true;
    }

    // 其他参数
    if (!this.otherParams.isDone()) {
      const otherParame = this.otherParams.getJson();
      this.setMaterialParam(otherParame);
      keepRun = true;
    }

    if (!keepRun) {
      this.object3d.removeEventListener(Object3DEvent.BEFORE_RENDER, this.setTransfromParame);
    }
  }

  // 设置各种纹理的方式
  setMap(mapName: string, map?: THREE.Texture | string | undefined) {
    // 如果材质不支持这个纹理的话就不忘下执行了
    if (!this.object3d.material.hasOwnProperty(mapName)) {
      return;
    }

    const { textureLoader, renderer } = this.context;
    const {
      envMapRefraction = true, //反射环境贴图 为false时是折射
    } = this.props;

    // // 设置最大各向异性 处理模糊问题
    let anisotropy = renderer.capabilities.getMaxAnisotropy();

    // 纹理置空
    if (_.isEmpty(map)) {
      (this.object3d.material as any)[mapName] = null;

      // 直接赋值
    } else if (map && (map as THREE.Texture).isTexture) {
      (map as THREE.Texture).anisotropy = anisotropy;

      (this.object3d.material as any)[mapName] = map;
    } else if (_.isString(map)) {
      // 从缓存中去取
      let texture = null;

      // 如果是环境纹理的话需要克隆一份单独加上折射参数
      if (mapName === "envMap") {

        if (this.envMap && this.envMap.uuid === map) {
          texture = this.envMap;
        } else {
          texture = this.envMap = textureLoader.load(map);
          texture.uuid = map;
        }

        texture.mapping = envMapRefraction
          ? THREE.EquirectangularRefractionMapping
          : THREE.EquirectangularReflectionMapping;

      } else {

        // 缓存中也没有就去加载
        texture = textureCache.get(map);
        if (!texture) {
          texture = textureLoader.load(map);
          textureCache.set(map, texture);
        }
      }

      texture!.anisotropy = anisotropy;

      (this.object3d.material as any)[mapName] = texture;
    }
  }

  render() {
    const { loadState } = this.state;

    let text = "";

    switch (loadState) {
      case LoadState.EMPTY:
        text = "没有设置模型";
        break;
      case LoadState.LOADDING:
        text = "模型加载中";
        break;
      case LoadState.LOAD_ERR:
        text = "模型加载失败";
        break;
      default:
        return null;
    }

    return (
      <MeshSeat
        {...{ text, ...this.props }}
        position={{}}
        rotarion={{}}
        scale={{}}
        lookAt={undefined}
        clipping={false}
      />
    );
  }
}
